package main

import (
	"context"
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"time"
)

func main() {
	var (
		config clientv3.Config
		client *clientv3.Client
		err error
		lease clientv3.Lease
		leaseGrantResp *clientv3.LeaseGrantResponse
		leaseid clientv3.LeaseID
		kv clientv3.KV
		putResp *clientv3.PutResponse
		getResp *clientv3.GetResponse
		keepResp *clientv3.LeaseKeepAliveResponse
		keepRespChan <-chan *clientv3.LeaseKeepAliveResponse
	)

	// 客户端配置
	config = clientv3.Config{
		Endpoints:            []string{"127.0.0.1:2379"}, // 集群列表
		DialTimeout:          5 * time.Second,
	}

	// 建立一个客户端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// 申请一个lease (租约)
	lease = clientv3.NewLease(client)

	// 申请一个10秒的租约
	if leaseGrantResp, err = lease.Grant(context.TODO(), 10); err != nil { // ttl值：过期时间
		fmt.Println(err)
		return
	}

	// 拿到租约的ID
	leaseid = leaseGrantResp.ID

	//ctx, _ := context.WithTimeout(context.TODO(), 5 * time.Second)
	// 续租了5秒，停止了续租，10秒的生命期 = 15秒的生命期
	// 5秒后会取消自动续租：lease.KeepAlive(ctx, leaseid)

	// 自动续租  KeepAliveOnce：续租一次，一般很少用
	if keepRespChan, err = lease.KeepAlive(context.TODO(), leaseid); err != nil {
		fmt.Println(err)
		return
	}

	// 处理续租应答的协程
	go func() {
		for {
			select {
			case keepResp = <- keepRespChan: // 每次心跳的应答请求
				if keepRespChan == nil { // 续租异常，应答会返回一个nil (判断keepResp效果相同)
					fmt.Println("租约已经失效了")
					goto END // 跳到指定的标签位置 (一般是跳出循环)
				} else { // 每秒会续租一次，所以会收到一次应答
					fmt.Println("收到自动续租应答： ", keepResp.ID)
				}
			}
		}
		END:
	}()

	// 租约过期原因：
	// 1. 网络原因租约太久没有续租，租约过期
	// 2. 主动取消自动续租里面的context

	// 获得kv API子集
	kv = clientv3.NewKV(client)

	// Put一个KV，让它与租约关联起来，从而实现10秒后自动过期
	if putResp, err = kv.Put(context.TODO(), "/cron/lock/job1", "", clientv3.WithLease(leaseid)); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("写入成功： ",putResp.Header.Revision)

	// 租约过期机制，主要是用于程序宕机后锁不会永远占用

	// 定时的看一下key过期了没有
	for {
		if getResp, err = kv.Get(context.TODO(), "/cron/lock/job1"); err != nil {
			fmt.Println(err)
			return
		}
		if getResp.Count == 0 {
			fmt.Println("kv过期了")
			break
		}
		fmt.Println("还没过期", getResp.Kvs)
		time.Sleep(2 * time.Second)
	}
}
